/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Joystick.h"
#include "ForceEffect.h"
#include <logs/Log.h>
#include <Gluck.h>
#include <Atom.h>
#include <sstream>

namespace liba
{
namespace input
{
namespace hard
{
namespace directx8
{

static const std::string axis_x_name( "Axis_X" );
static const std::string axis_y_name( "Axis_Y" );
static const std::string axis_z_name( "Axis_Z" );
static const std::string axis_rot_x_name( "Axis_Rot_X" );
static const std::string axis_rot_y_name( "Axis_Rot_Y" );
static const std::string axis_rot_z_name( "Axis_Rot_Z" );

static const std::string slider_name( "Slider_" );
static const std::string pov_controller_name( "Pov_" );
static const std::string button_name( "Button_" );

//const Atom Joystick::Volume("Volume");

hard::ForceEffect * Joystick::create_force_effect(const filesystem::String & effect_name)
{
	return new ForceEffect( device, effect_name );
}

BOOL CALLBACK Joystick::ObjectCallback( LPCDIDEVICEOBJECTINSTANCE instance, LPVOID ref )
{
	Joystick * joystick = (Joystick *) ref;

	if( instance->dwType & DIDFT_RELAXIS )
		return DIENUM_CONTINUE;

	int instance_number = DIDFT_GETINSTANCE( instance->dwType );

    // For axes that are returned, set the DIPROP_RANGE property for the
    // enumerated axis in order to scale min/max values.
    if( instance->dwType & DIDFT_ABSAXIS )
    {
		DIPROPRANGE diprg;
		diprg.diph.dwSize       = sizeof( DIPROPRANGE );
		diprg.diph.dwHeaderSize = sizeof( DIPROPHEADER );
		diprg.diph.dwHow        = DIPH_BYID;
		diprg.diph.dwObj        = instance->dwType; // Specify the enumerated axis
		diprg.lMin              = -10000;
		diprg.lMax              = +10000;

		// Set the range for the axis
		if( FAILED( joystick->device->SetProperty( DIPROP_RANGE, &diprg.diph ) ) )
		{
			logs::log() << logs::warning << "Failed to set joystick axis property. Skipping axis\n";
			return DIENUM_CONTINUE;
		}
    }

    if( instance->guidType == GUID_XAxis )
    {
		joystick->axis.insert( std::make_pair( axis_x_name, AxisState( Manager::AXIS, joystick->name, "Axis_X" ) ) );
    }
    if( instance->guidType == GUID_YAxis )
    {
		joystick->axis.insert( std::make_pair( axis_y_name, AxisState( Manager::AXIS, joystick->name, "Axis_Y" ) ) );
    }
    if( instance->guidType == GUID_ZAxis )
    {
		joystick->axis.insert( std::make_pair( axis_z_name, AxisState( Manager::AXIS, joystick->name, "Axis_Z" ) ) );
    }
    if( instance->guidType == GUID_RxAxis )
    {
		joystick->axis.insert( std::make_pair( axis_rot_x_name, AxisState( Manager::AXIS, joystick->name, "Axis_Rot_X" ) ) );
    }
    if( instance->guidType == GUID_RyAxis )
    {
		joystick->axis.insert( std::make_pair( axis_rot_y_name, AxisState( Manager::AXIS, joystick->name, "Axis_Rot_Y" ) ) );
    }
    if( instance->guidType == GUID_RzAxis )
    {
		joystick->axis.insert( std::make_pair( axis_rot_z_name, AxisState( Manager::AXIS, joystick->name, "Axis_Rot_Z" ) ) );
    }
    if( instance->guidType == GUID_Slider )
    {
		std::stringstream ss;
		ss << slider_name << joystick->slider_count++;
		joystick->axis.insert( std::make_pair( ss.str(), AxisState( Manager::AXIS, joystick->name, ss.str() ) ) );
    }
    if( instance->guidType == GUID_POV )
    {
		std::stringstream ss;
		ss << pov_controller_name << joystick->pov_count++;
		joystick->axis.insert( std::make_pair( ss.str(), AxisState( Manager::POV, joystick->name, ss.str(), new PressStrategyPOV ) ) );
    }
    if( instance->guidType == GUID_Button )
    {
		std::stringstream ss;
		ss << button_name << joystick->button_count++;
		joystick->axis.insert( std::make_pair( ss.str(), AxisState( Manager::BUTTON, joystick->name, ss.str() ) ) );
    }

	return DIENUM_CONTINUE;
}

Joystick::Joystick( IDirectInputDevice8 * device, const std::string & name )
:	device( device ),
	name( name ),
	valid( false ),
	button_count( 0 ),
	pov_count( 0 ),
	slider_count( 0 )
{
	device->EnumObjects( ObjectCallback, this, DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV );

	if( device->SetDataFormat( &c_dfDIJoystick ) != DI_OK )
	{
		device->Release(); device = 0;
		return;
	}

	if( device->Acquire() != DI_OK )
	{
		device->Release(); device = 0;
		return;
	}

	valid = true;
}

void Joystick::poll( int ms_count )
{
    HRESULT hr;
    DIJOYSTATE js;

    // poll the joystick to read the current state
	hr = device->Poll();
	hr = device->GetDeviceState(sizeof(js), &js); 
	if( hr == DIERR_INPUTLOST )
	{
		hr = device->Acquire();
		hr = device->GetDeviceState(sizeof(js), &js); 
	}
	if( hr == DI_OK )
	{	
		//obtaining state of joystick
		AxisState * axis_state = get_axis( axis_x_name );
		if( axis_state )
			axis_state->record_value( js.lX / 10000.0f );

		axis_state = get_axis( axis_y_name );
		if( axis_state )
			axis_state->record_value( js.lY / 10000.0f );

		axis_state = get_axis( axis_z_name );
		if( axis_state )
			axis_state->record_value( js.lZ / 10000.0f );

		axis_state = get_axis( axis_rot_x_name );
		if( axis_state )
			axis_state->record_value( js.lRx / 10000.0f );

		axis_state = get_axis( axis_rot_y_name );
		if( axis_state )
			axis_state->record_value( js.lRy / 10000.0f );

		axis_state = get_axis( axis_rot_z_name );
		if( axis_state )
			axis_state->record_value( js.lRz / 10000.0f );

		int i;
		for( i = 0; i < 2; ++i )
		{
			std::stringstream ss;
			ss << slider_name << i;
			AxisState * axis_state = get_axis( ss.str() );
			if( axis_state )
				axis_state->record_value( js.rglSlider[i] / 10000.0f );
		}

		for( i = 0; i < 4; ++i )
		{
			std::stringstream ss;
			ss << pov_controller_name << i;
			AxisState * axis_state = get_axis( ss.str() );
			if( !axis_state )
				continue;

			if( LOWORD( js.rgdwPOV[i] ) == 0xFFFF )
				axis_state->record_value( -1 );	//it's centered;
			else
				axis_state->record_value( js.rgdwPOV[i] / 100.0f );	//it's in hundredth of gradus.
		}

		for( i = 0; i < 8; ++i )
		{
			std::stringstream ss;
			ss << button_name << i;
			AxisState * axis_state = get_axis( ss.str() );
			if( axis_state )
				axis_state->record_value( !!(js.rgbButtons[i] & 0x80) );
		}
	}
}

Joystick::~Joystick()
{
	device->Release(); device = 0;
}

std::string Joystick::get_name()const
{
	return name;
}

/*bool Joystick::ChangeAttribute(const Atom & name, double value)
{
	if( name == Volume )
	{
		DIPROPDWORD prop;
		prop.diph.dwHeaderSize = sizeof( prop.diph );
		prop.diph.dwHow = DIPH_DEVICE;
		prop.diph.dwObj = 0;
		prop.diph.dwSize = sizeof( prop );
		prop.dwData = DWORD(value * 10000);

		device->SetProperty( DIPROP_FFGAIN, &prop.diph );
		return true;
	}

	return false;
}*/

double Joystick::get_axis_position( const std::string & axis_name )const
{
	Axis::const_iterator it = axis.find( axis_name );
	if( it != axis.end() )
		return it->second.get_value();

	return 0;
}

Manager::AxisType Joystick::get_axis_type( const std::string & axis_name )const
{
	Axis::const_iterator it = axis.find( axis_name );
	if( it != axis.end() )
		return it->second.get_axis_type();

	return Manager::NONE;
}

} // namespace directx8
} // namespace hard
} // namespace input
} // namespace liba



